﻿using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CSharpLogger.Client;
using CSharpLogger.Common;
using Xunit;

namespace CSharpLogger.Test
{
    public class FileLoggerTest : CSharpLoggerTestBase, IDisposable
    {
        /// <summary>
        /// 
        /// </summary>
        LoggerHandlerFactory factory;

        /// <summary>
        /// 
        /// </summary>
        FileLogger logger;

        /// <summary>
        /// Tries to log a message using default configuation
        /// </summary>
        public FileLoggerTest()
        {
            base.Init();

            factory = new LoggerHandlerFactory();
            logger = factory.GetHandler<FileLogger>();
        }

        /// <summary>
        /// 
        /// </summary>
        [Fact]
        public void LogDefaultTest()
        {
            //arrange 
            var expected = "Hello CSharpLogger";

            var factory = new LoggerHandlerFactory();
            var logger = factory.GetHandler<FileLogger>();

            //act
            logger.Log(expected);

            //asert
            var actual = ReadTestFile(logger.Path, logger.FileName);
            Assert.True(string.IsNullOrEmpty(actual));
        }

        /// <summary>
        /// Verifies that only log messages lower than DEBUG are logged
        /// </summary>
        [Fact(DisplayName = "DebugLevelTest")]
        public void LogDebugLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);


            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.True(actual.Contains("ERROR"));
            Assert.True(actual.Contains("WARN"));
            Assert.True(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Verifies that only log messages lower than WARN are logged
        /// </summary>
        [Fact(DisplayName = "WarnLevelTest")]
        public void LogWarnLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.WARN);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);

            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.True(actual.Contains("ERROR"));
            Assert.True(actual.Contains("WARN"));
            Assert.False(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Verifies that only log messages lower than ERROR are logged
        /// </summary>
        [Fact(DisplayName = "ErrorLevelTest")]
        public void LogErrorLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.ERROR);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);

            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.True(actual.Contains("ERROR"));
            Assert.False(actual.Contains("WARN"));
            Assert.False(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Verifies that only log messages lower than FATAL are logged
        /// </summary>
        [Fact(DisplayName = "FatalLevelTest")]
        public void LogFatalLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.FATAL);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);

            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.False(actual.Contains("ERROR"));
            Assert.False(actual.Contains("WARN"));
            Assert.False(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Verifies that only log messages lower than INFO are logged
        /// </summary>
        [Fact(DisplayName = "InfoLevelTest")]
        public void LogInfoLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.INFO);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);

            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.False(actual.Contains("FATAL"));
            Assert.False(actual.Contains("ERROR"));
            Assert.False(actual.Contains("WARN"));
            Assert.False(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Verifies that only log messages lower than OFF are logged
        /// </summary>
        [Fact(DisplayName = "OffLevelTest")]
        public void LogOffLevelTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.OFF);

            //act
            logger.Log("This is a DEBUG log", LogLevel.DEBUG);
            logger.Log("This is a ERROR log", LogLevel.ERROR);
            logger.Log("This is a FATAL log", LogLevel.FATAL);
            logger.Log("This is a INFO log", LogLevel.INFO);
            logger.Log("This is a WARN log", LogLevel.WARN);
            logger.Log("This is a OFF log", LogLevel.OFF);

            //assert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.False(actual.Contains("INFO"));
            Assert.False(actual.Contains("FATAL"));
            Assert.False(actual.Contains("ERROR"));
            Assert.False(actual.Contains("WARN"));
            Assert.False(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Log multiple messages at the same time
        /// </summary>
        [Fact(DisplayName = "MultithreadingTest")]
        public void MultithreadingLogTest()
        {
            //arrange 
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var total = 1000;
            Thread[] thread = new Thread[total];

            for (int i = 0; i < total; i++)
            {
                thread[i] = new Thread(x =>
                {
                    logger.Log("This is a DEBUG log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.DEBUG);
                    logger.Log("This is a ERROR log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.ERROR);
                    logger.Log("This is a FATAL log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.FATAL);
                    logger.Log("This is a INFO log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.INFO);
                    logger.Log("This is a WARN log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.WARN);
                    logger.Log("This is a OFF log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.OFF);
                });
            }

            for (int i = 0; i < total; i++)
            {
                thread[i].Start(i);
                thread[i].Join();
            }

            //asert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.True(actual.Contains("ERROR"));
            Assert.True(actual.Contains("WARN"));
            Assert.True(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// Log multiple messages using Taks
        /// </summary>
        [Fact(DisplayName = "MultitaskTest")]
        public void LogMultipleThreadsTest()
        {
            //arrange 
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var parallelOptions = new ParallelOptions()
            {
                MaxDegreeOfParallelism = Environment.ProcessorCount
            };

            var action = new Action<int>(x =>
            {
                logger.Log("This message shouldn't be logged" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.OFF);
                logger.Log("This is a INFO log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.INFO);
                logger.Log("This is a FATAL log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.FATAL);
                logger.Log("This is a ERROR log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.ERROR);
                logger.Log("This is a WARN log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.WARN);
                logger.Log("This is a DEBUG log" + " " + x + " Current ThreadId:" + Thread.CurrentThread.ManagedThreadId, LogLevel.DEBUG);

            });

            //act
            Parallel.For(0, 1000, parallelOptions, action);

            //asert
            var actual = ReadTestFile(logger.Path, logger.FileName);

            Assert.False(actual.Contains("OFF"));
            Assert.True(actual.Contains("INFO"));
            Assert.True(actual.Contains("FATAL"));
            Assert.True(actual.Contains("ERROR"));
            Assert.True(actual.Contains("WARN"));
            Assert.True(actual.Contains("DEBUG"));

            this.TestCleanUp(logger.Path, logger.FileName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <returns></returns>
        private static string ReadTestFile(string path, string filename)
        {
            using (FileStream fs = new FileStream(string.Concat(path, filename), FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (StreamReader sr = new StreamReader(fs, Encoding.ASCII))
                {
                    try
                    {
                        return sr.ReadToEnd();
                    }
                    catch
                    {
                        return string.Empty;
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        private void TestCleanUp(string path, string filename)
        {
            File.Delete(string.Concat(path, filename));
        }

        public void Dispose()
        {

        }
    }
}
